﻿// Copyright (c) Microsoft Corporation.  All rights reserved. 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace WiE.Model
{
    class LocationHistoryChangedArgs : DataObjectChangedArgs
    {
        public LocationHistoryChangedArgs() { }

        public LocationHistoryChangedArgs(WiELocationDataObject p_locationDataObject) : base(p_locationDataObject) { }

        public LocationHistoryChangedArgs(WiELocationDataObject p_locationDataObject, ChangeType p_changeType) : base(p_locationDataObject, p_changeType) { }

        WiELocationDataObject LocationChanged
        {
            set { base.m_wieDataObject = value; }
            get { return (WiELocationDataObject)base.m_wieDataObject; }
        }
    }

    /// <summary>
    /// A WiELocation is a DataObject that represents the location (long/lat/alt), heading and velocity of a device (guidDeviceID) belonging to a 
    /// WiE Community Member (guidMemberID) at a given point in time.
    /// </summary>
    [DataContract]
    class WiELocationDataObject : WiEDataObject
    {
        // Property Key definitions when Location Data Object for use when object is converted into a property bag...
        public const string KEY_LOCATIONID = "LocationID";
        public const string KEY_MEMBERID = "MemberID";
        public const string KEY_DEVICEID = "DeviceID";
        public const string KEY_LONGITUDE = "Longitude";
        public const string KEY_LATITUDE = "Latitude";
        public const string KEY_SPEED = "Speed";
        public const string KEY_HEADING = "Heading";
        public const string KEY_ALTITUDEWRTELLIPSOID = "AltitudeWRTEllipsoid";
        public const string KEY_ALTITUDEWRTSEALEVEL = "AltitudeWRTSeaLevel";
        public const string KEY_NUMSATELLITES = "NumSatellites";
        public const string KEY_DATECOLLECTED = "DateCollected";

        public static int CompareDateCollectedForDescendingOrder(WiELocationDataObject p_obj1, WiELocationDataObject p_obj2)
        {
            // Less than 0  p_obj1 is less than p_obj2.
            // 0 p_obj1 equals p_obj2.
            // Greater than 0 p_obj1 is greater than p_obj2.

            if (p_obj1.DateCollected > p_obj2.DateCollected)
                return -1;
            else if (p_obj1.DateCollected < p_obj2.DateCollected)
                return 1;
            else
                return 0;
        }

        public static int CompareDateCollectedForAscendingOrder(WiELocationDataObject p_obj1, WiELocationDataObject p_obj2)
        {
            return (-1*CompareDateCollectedForDescendingOrder(p_obj1,p_obj2));
        }


        // Member / properties for the Location Data Object (could be represented as an internal dictionary at a later date, for
        // now we convert on demand as needed).
        private System.Guid m_guidDeviceID;
        private System.Guid m_guidMemberID;
        private System.Guid m_guidLocationID;
        private int m_nNumSatellites = 0;
        private Double? m_flLongitude = null;
        private Double? m_flLatitude = null;
        private Double? m_flSpeed = null;
        private Double? m_flHeading = null;
        private Double? m_flAltitudeWRTSeaLevel = null;
        private Double? m_flAltitudeWRTEllipsoid = null;
        private DateTime? m_dtCollectedUTC = null;

        public WiELocationDataObject()
        {
        }

        /// <summary>
        /// Constructor for Basic Location
        /// </summary>
        /// <param name="p_guidDeviceID"></param>
        /// <param name="p_guidMemberID"></param>
        /// <param name="p_guidLocationID"></param>
        /// <param name="p_flLongitude"></param>
        /// <param name="p_flLatitude"></param>
        /// <param name="p_dtCollected"></param>
        public WiELocationDataObject(System.Guid p_guidMemberID, System.Guid p_guidDeviceID, System.Guid p_guidLocationID, Double p_flLongitude, Double p_flLatitude, DateTime p_dtCollected)
        {
            DeviceID = p_guidDeviceID;
            MemberID = p_guidMemberID;
            LocationID = p_guidLocationID;
            Longitude = p_flLongitude;
            Latitude = p_flLatitude;
            DateCollected = p_dtCollected;
        }

        /// <summary>
        /// Constructor for Location with Speed and Heading details
        /// </summary>
        /// <param name="p_guidDeviceID"></param>
        /// <param name="p_guidMemberID"></param>
        /// <param name="p_guidLocationID"></param>
        /// <param name="p_flLongitude"></param>
        /// <param name="p_flLatitude"></param>
        /// <param name="p_flSpeed"></param>
        /// <param name="p_flHeading"></param>
        /// <param name="p_nNumSatellites"></param>
        /// <param name="p_dtCollected"></param>
        public WiELocationDataObject(System.Guid p_guidMemberID, System.Guid p_guidDeviceID, System.Guid p_guidLocationID, Double p_flLongitude, Double p_flLatitude, Double p_flSpeed, Double p_flHeading, int p_nNumSatellites, DateTime p_dtCollected)
        {
            DeviceID = p_guidDeviceID;
            MemberID = p_guidMemberID;
            LocationID = p_guidLocationID;
            Longitude = p_flLongitude;
            Latitude = p_flLatitude;
            Speed = p_flSpeed;
            Heading = p_flHeading;
            NumSatellites = p_nNumSatellites;
            DateCollected = p_dtCollected;
        }

        /// <summary>
        /// Constructor for Location with all details including Altitude
        /// </summary>
        /// <param name="p_guidDeviceID"></param>
        /// <param name="p_guidMemberID"></param>
        /// <param name="p_guidLocationID"></param>
        /// <param name="p_flLongitude"></param>
        /// <param name="p_flLatitude"></param>
        /// <param name="p_flSpeed"></param>
        /// <param name="p_flHeading"></param>
        /// <param name="p_flAltitudeWRTSeaLevel"></param>
        /// <param name="p_flAltitudeWRTEllipsoid"></param>
        /// <param name="p_nNumSatellites"></param>
        /// <param name="p_dtCollected"></param>
        public WiELocationDataObject(System.Guid p_guidMemberID, System.Guid p_guidDeviceID, System.Guid p_guidLocationID, Double p_flLongitude, Double p_flLatitude, Double p_flSpeed, Double p_flHeading, Double p_flAltitudeWRTSeaLevel, Double p_flAltitudeWRTEllipsoid, int p_nNumSatellites, DateTime p_dtCollected)
        {
            DeviceID = p_guidDeviceID;
            MemberID = p_guidMemberID;
            LocationID = p_guidLocationID;
            Longitude = p_flLongitude;
            Latitude = p_flLatitude;
            Speed = p_flSpeed;
            Heading = p_flHeading;
            NumSatellites = p_nNumSatellites;
            AltitudeWRTEllipsoid = p_flAltitudeWRTEllipsoid;
            AltitudeWRTSeaLevel = p_flAltitudeWRTSeaLevel;
            DateCollected = p_dtCollected;
        }

        /// <summary>
        /// Constructor for location using a property bag of key value pairs to populate the LocationDataObject properties.
        /// </summary>
        /// <remarks>
        /// Assume that the property bag can contain either the correct type or string values that need to be converted back to the 
        /// correct types (value = Type.Parse(Item.Value.ToString())
        /// </remarks>
        /// <param name="p_propertyBag"></param>
        public WiELocationDataObject(Dictionary<string, object> p_propertyBag)
        {
            foreach (KeyValuePair<string, object> kvp in p_propertyBag)
            {
                switch (kvp.Key)
                {
                    case KEY_DEVICEID:
                        DeviceID = new System.Guid(kvp.Value.ToString());
                        break;
                    case KEY_MEMBERID:
                        MemberID = new System.Guid(kvp.Value.ToString());
                        break;
                    case KEY_LOCATIONID:
                        LocationID = new System.Guid(kvp.Value.ToString());
                        break;
                    case KEY_LONGITUDE:
                        Longitude = Double.Parse(kvp.Value.ToString());
                        break;
                    case KEY_LATITUDE:
                        Latitude = Double.Parse(kvp.Value.ToString());
                        break;
                    case KEY_SPEED:
                        Speed = Double.Parse(kvp.Value.ToString());
                        break;
                    case KEY_HEADING:
                        Heading = Double.Parse(kvp.Value.ToString());
                        break;
                    case KEY_ALTITUDEWRTELLIPSOID:
                        AltitudeWRTEllipsoid = Double.Parse(kvp.Value.ToString());
                        break;
                    case KEY_ALTITUDEWRTSEALEVEL:
                        AltitudeWRTSeaLevel = Double.Parse(kvp.Value.ToString());
                        break;
                    case KEY_NUMSATELLITES:
                        NumSatellites = Int32.Parse(kvp.Value.ToString());
                        break;
                    case KEY_DATECOLLECTED:
                        DateCollected = DateTime.Parse(kvp.Value.ToString());
                        break;
                }
            }
        }

        /// <summary>
        /// Override of DataObject UniqueID property, returns the location ID which can be used
        /// to uniquely refer to a single location data.
        /// </summary>
        public override Guid UniqueId
        {
            get { return m_guidLocationID; }
        }

        /// <summary>
        /// The unique ID for this location data 
        /// </summary>
        [DataMember]
        public System.Guid LocationID
        {
            set { m_guidLocationID = value; }
            get { return m_guidLocationID; }
        }

        /// <summary>
        /// The ID of the device that is associated with this location data
        /// </summary>
        [DataMember]
        public System.Guid DeviceID
        {
            set { m_guidDeviceID = value; }
            get { return m_guidDeviceID; }
        }

        /// <summary>
        /// The ID of the member that is associatd with this location data
        /// </summary>
        [DataMember]
        public System.Guid MemberID
        {
            set { m_guidMemberID = value; }
            get { return m_guidMemberID; }
        }

        /// <summary>
        /// The Longitude for this location
        /// </summary>
        [DataMember]
        public Double? Longitude
        {
            set { m_flLongitude = value; }
            get { return m_flLongitude; }
        }

        /// <summary>
        /// The Latitude for this location
        /// </summary>
        [DataMember]
        public Double? Latitude
        {
            set { m_flLatitude = value; }
            get { return m_flLatitude; }
        }

        /// <summary>
        /// The Speed associated with the device at the time the location was captured, null if unknown.
        /// </summary>
        [DataMember]
        public Double? Speed
        {
            set { m_flSpeed = value; }
            get { return m_flSpeed; }
        }

        /// <summary>
        /// The Heading associated with the device at the time the location was captured, null if unknown.
        /// </summary>
        [DataMember]
        public Double? Heading
        {
            set { m_flHeading = value; }
            get { return m_flHeading; }
        }

        /// <summary>
        /// The Altitude with respect to Sea Level for the device at the time the location was captured, null if unknown.
        /// </summary>
        [DataMember]
        public Double? AltitudeWRTSeaLevel
        {
            set { m_flAltitudeWRTSeaLevel = value; }
            get { return m_flAltitudeWRTSeaLevel; }
        }

        /// <summary>
        /// The Altitude with respect to earth as an ellipsoid for the device at the time the location was captured, null if unknown.
        /// </summary>
        [DataMember]
        public Double? AltitudeWRTEllipsoid
        {
            set { m_flAltitudeWRTEllipsoid = value; }
            get { return m_flAltitudeWRTEllipsoid; }
        }

        /// <summary>
        /// The Number of Satellites that were active in the solution to arrive at the location.
        /// </summary>
        [DataMember]
        public int NumSatellites
        {
            set { m_nNumSatellites = value; }
            get { return m_nNumSatellites; }
        }

        /// <summary>
        /// The date and time in GMT that the location was acquired.
        /// </summary>
        [DataMember]
        public DateTime? DateCollected
        {
            set { m_dtCollectedUTC = value; }
            get { return m_dtCollectedUTC; }
        }

        /// <summary>
        /// Returns this LocationDataObject as a property bag of name-value pairs (where key=string and value=object).
        /// </summary>
        /// <returns>
        /// Dictionary of Properties (as string values)
        /// </returns>
        public override Dictionary<string, object> ToPropertyDictionary()
        {
            Dictionary<string, object> dictionaryProperties = new Dictionary<string, object>();

            if (DeviceID != null)
                dictionaryProperties.Add(KEY_DEVICEID, DeviceID.ToString());

            if (MemberID != null)
                dictionaryProperties.Add(KEY_MEMBERID, MemberID.ToString());

            if (LocationID != null)
                dictionaryProperties.Add(KEY_LOCATIONID, LocationID.ToString());

            if (Longitude != null)
                dictionaryProperties.Add(KEY_LONGITUDE, new Decimal((double)Longitude));

            if (Latitude != null)
                dictionaryProperties.Add(KEY_LATITUDE, new Decimal((double)Latitude));

            if (Speed != null)
                dictionaryProperties.Add(KEY_SPEED, new Decimal((double)Speed));

            if (Heading != null)
                dictionaryProperties.Add(KEY_HEADING, new Decimal((double)Heading));

            if (AltitudeWRTEllipsoid != null)
                dictionaryProperties.Add(KEY_ALTITUDEWRTELLIPSOID, new Decimal((double)AltitudeWRTEllipsoid));

            if (AltitudeWRTSeaLevel != null)
                dictionaryProperties.Add(KEY_ALTITUDEWRTSEALEVEL, new Decimal((double)AltitudeWRTSeaLevel));

            dictionaryProperties.Add(KEY_NUMSATELLITES, new Decimal((int)NumSatellites));

            if (DateCollected != null)
                dictionaryProperties.Add(KEY_DATECOLLECTED, DateCollected);

            return dictionaryProperties;
        }

        /// <summary>
        /// Converts degrees to radians
        /// </summary>
        /// <param name="p_degrees"></param>
        /// <returns></returns>
        private double ToRadians(double p_degrees)
        {
            const double dRadianConvert = Math.PI / 180.0;

            double dblRadians = p_degrees * dRadianConvert;

            return (dblRadians);
        }

        /// <summary>
        /// Calculates the distance in meters between this LocationObject and the location object specifed in p_destinationLocationObject
        /// 
        /// Uses the law of cosines to calculate the distance.
        /// </summary>
        /// <param name="p_destinationLocationObject"></param>
        /// <returns></returns>
        public Double DistanceTo(WiELocationDataObject p_destinationLocationObject)
        {
            const double dEarthRadiusInMeters = 6371000;
            double dDistance = 0;

            double dLatitudeSource = ToRadians((double)Latitude);
            double dLongitudeSource = ToRadians((double)Longitude);
            double dLatitudeDestination = ToRadians((double)p_destinationLocationObject.Latitude);
            double dLongitudeDestination = ToRadians((double)p_destinationLocationObject.Longitude);

            double a = Math.Sin(dLatitudeSource) * Math.Sin(dLatitudeDestination);
            double b = Math.Cos(dLatitudeSource) * Math.Cos(dLatitudeDestination) * Math.Cos(dLongitudeDestination - dLongitudeSource);
            double c = Math.Acos(a + b);

            dDistance = Math.Abs(dEarthRadiusInMeters * c);

            return dDistance;
        }

    }
}
